package org.dreamwork.modbus.tcp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

public class Server {
    public  static final int DEFAULT_PORT = 502;
    private static final int CLOSE_PORT   = 65502;

    private final int port;
    private final AtomicBoolean running = new AtomicBoolean (true);
    private final InetAddress localhost;
    private final Logger logger = LoggerFactory.getLogger (Server.class.getSimpleName ());

    public Server () throws UnknownHostException {
        this (DEFAULT_PORT);
    }

    public Server (int port) throws UnknownHostException {
        this.port = port;
        localhost = InetAddress.getByName ("127.0.0.1");
    }

    public Server bind (IServerHandler listener) {
        ExecutorService executor = Executors.newCachedThreadPool ();

        executor.execute (() -> {
            try (ServerSocket server = new ServerSocket (port)) {
                server.setReuseAddress (true);

                Runtime.getRuntime ().addShutdownHook (new Thread (this::unbind));
                List<Worker> container = new ArrayList<> ();
                logger.info ("Modbus TCP Server listen on: " + port);

                while (running.get ()) {
                    Socket socket = server.accept ();
                    InetAddress remote = socket.getInetAddress ();
                    int port = socket.getPort ();
                    if (port == CLOSE_PORT && remote.equals (localhost)) {
                        break;
                    }

                    executor.execute (new Worker (socket, listener, container));
                }
                if (logger.isTraceEnabled ()) {
                    logger.trace ("main loop done!");
                }
                if (!container.isEmpty ()) {
                    container.forEach (Worker::abort);
                }
                executor.shutdown ();
                logger.info ("Modbus TCP Server shutdown.");
            } catch (IOException ex) {
                logger.warn (ex.getMessage (), ex);
            }
        });
        return this;
    }

    public void unbind () {
        if (running.get ()) {
            running.set (false);

            try (Socket socket = new Socket (localhost, port, localhost, CLOSE_PORT)) {
                OutputStream out = socket.getOutputStream ();
                out.write (0);
            } catch (IOException ex) {
                logger.warn (ex.getMessage (), ex);
            }
        }
    }
}